package token

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"time"

	"gitee.com/xinshortlink/internal/pkg/conf"
	"gitee.com/xinshortlink/internal/pkg/token/jwt"
	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
)

const tokenKey = "xin:token:"

type Token struct {
	logger *zap.Logger
	rdb    *redis.Client
	Conf   *conf.TokenConf // 配置信息
}

func NewToken(logger *zap.Logger, rdb *redis.Client, conf *conf.Conf) (*Token, error) {
	return &Token{
		logger: logger,
		rdb:    rdb,
		Conf:   conf.Token,
	}, nil
}

// Generate 生成token
func (t *Token) Generate(ctx context.Context, account *LoginAccount) (*AccessToken, error) {
	token, err := jwt.GenerateToken(account.AccountName, t.Conf.SignKey)
	if err != nil {
		return nil, err
	}

	account.AccessToken = token
	account.Expire = t.Conf.Expire

	key := tokenKey + account.AccountName

	v, err := json.Marshal(account)
	if err != nil {
		return nil, err
	}

	_, err = t.rdb.SetEx(ctx, key, v, time.Duration(t.Conf.Expire)*time.Second).Result()
	if err != nil {
		return nil, err
	}

	return &AccessToken{AccessToken: token, Expire: t.Conf.Expire}, nil
}

// ParseToken 解析 token
func (t *Token) ParseToken(ctx context.Context, token string) (*LoginAccount, error) {
	accountName, err := jwt.ParseToken(token, t.Conf.SignKey)
	if err != nil {
		return nil, err
	}
	key := tokenKey + accountName

	val, err := t.rdb.Get(ctx, key).Result()
	if err != nil {
		if errors.Is(err, redis.Nil) {
			return nil, nil
		}

		return nil, err
	}

	if val == "" {
		return nil, nil
	}

	account := &LoginAccount{}
	err = json.Unmarshal([]byte(val), account)
	if err != nil {
		return nil, err
	}

	return account, nil
}

func (t *Token) Clear(ctx context.Context, token string) error {
	temp, err := t.ParseToken(ctx, token)
	if err != nil {
		return err
	}
	key := tokenKey + temp.AccountName
	return t.rdb.Del(ctx, key).Err()
}

type AccessToken struct {
	AccessToken string `json:"accessToken"` // access token
	Expire      int    `json:"expire"`      // 有效期，单位秒
}

func (a *AccessToken) String() string {
	return fmt.Sprintf(`{"accessToken":"%s","expire":%d}`, a.AccessToken, a.Expire)
}
